package com.xiaoxi.logaspectcommon.service.aspect;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiaoxi.common.exception.BMSException;
import com.xiaoxi.common.response.ApiResponse;
import com.xiaoxi.common.util.AuthUtils;
import com.xiaoxi.domain.log.BMSLogOperation;
import com.xiaoxi.domain.user.BMSUserContext;
import com.xiaoxi.feign.logserver.LogServer;
import com.xiaoxi.logaspectcommon.common.annotation.OperationLog;
import com.xiaoxi.logaspectcommon.common.constant.OperationConstants;
import com.xiaoxi.logaspectcommon.common.util.IPUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @ClassName UserOperationAspect
 * @Description
 * @Author xiaoxi
 * @Date 2022/10/30 20:27
 * @Version 1.0
 */
@Aspect
@Component
@Slf4j
public class UserOperationAspect {

    @Resource
    private LogServer logServer;

    @Resource
    private ObjectMapper objectMapper;

    @Value("${bms.xiaoxi.log.operation-log.enable:false}")
    private boolean operationEnable;

    /**
     * 指定切入点
     * 两种方式：
     * 1. 使用表达式进行匹配 --> 如果切入点目录发生改变就切入不到了 建议使用注解
     * 2. 使用注解，在方法上添加一个注解。然后指定添加了这个注解的方法都是切入点
     */
    @Pointcut("@annotation(com.xiaoxi.logaspectcommon.common.annotation.OperationLog)")
    public void operationLogPointCut() {
    }

    /**
     * 后置通知，保存操作成功 日志
     */
    @AfterReturning(pointcut = "operationLogPointCut()", returning = "result")
    public void afterReturningHandler(JoinPoint joinPoint, ApiResponse result) {
        log.info("记录成功操作日志 ： {}", operationEnable);
        if (operationEnable) {
            afterReturningLog(joinPoint, result);
        }
    }


    /**
     * 异常通知，保存操作失败 日志
     *
     * @param joinPoint
     * @param throwable
     */
    @AfterThrowing(value = "operationLogPointCut()", throwing = "throwable")
    public void afterThrowingHandler(JoinPoint joinPoint, Throwable throwable) {
        log.info("记录异常操作日志 ： {}", operationEnable);
        if (operationEnable) {
            afterThrowingLog(joinPoint, throwable);
        }
    }


    /**
     * 记录正常操作日志
     *
     * @param joinPoint
     * @param result
     */
    private void afterReturningLog(JoinPoint joinPoint, ApiResponse result) {

        BMSLogOperation bmsLogOperation = buildOperationLog(joinPoint);

        perfectOperationLog(bmsLogOperation, result);

        ApiResponse apiResponse = logServer.addLogOperation(bmsLogOperation);
        if (!"200".equals(apiResponse.getCode())) {
            log.error("操作日志记录异常：{}", apiResponse);
        }

    }


    /**
     * 记录异常操作日志
     *
     * @param joinPoint
     * @param throwable
     */
    private void afterThrowingLog(JoinPoint joinPoint, Throwable throwable) {

        BMSLogOperation bmsLogOperation = buildOperationLog(joinPoint);

        ApiResponse apiResponse = new ApiResponse();
        try {

            apiResponse = ApiResponse.ofException((BMSException) throwable);

            perfectOperationLog(bmsLogOperation, apiResponse);

            ApiResponse response = logServer.addLogOperation(bmsLogOperation);
            if (!"200".equals(response.getCode())) {
                log.error("操作日志记录异常：{}", response);
            }

        } catch (Exception e) {

            log.error("throwable parent class is not BMSException : {}", e.getMessage());
            apiResponse.setMessage(throwable.getMessage());

        }
    }

    /**
     * 初始化 操作日志
     *
     * @param joinPoint
     * @return
     */
    private BMSLogOperation buildOperationLog(JoinPoint joinPoint) {

        BMSLogOperation bmsLogOperation = new BMSLogOperation();
        // 当前请求信息
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        assert ra != null;
        HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest();
        bmsLogOperation.setIp(IPUtils.getIpAddr(request));
        bmsLogOperation.setUri(request.getRequestURI());
        bmsLogOperation.setRequestMethod(request.getMethod());

        // 记录url请求参数 及 body参数
        Map<String, Object> paramsMap = new HashMap<>();
        if (StringUtils.isNotBlank(request.getQueryString())) {
            paramsMap.put("query", request.getQueryString());
        }

        // 补充注解信息
        OperationLog operationLog = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(OperationLog.class);

        // 保存requestBody数据
        if (operationLog.saveRequestData()) {
            Object[] bodyParams = joinPoint.getArgs();
            if (Objects.nonNull(bodyParams)) {
                for (Object arg : bodyParams) {
                    paramsMap.put(arg.getClass().getSimpleName(), arg);
                }
            }
        }

        try {
            bmsLogOperation.setParams(objectMapper.writeValueAsString(paramsMap));
        } catch (JsonProcessingException e) {
            log.error("系统转换Json异常：{} --- {}", e.getMessage(), paramsMap);
        }

        bmsLogOperation.setOperationTime(LocalDateTime.now());
        bmsLogOperation.setOperation(OperationConstants.CODE + operationLog.operation());
        bmsLogOperation.setOperationDescribe(OperationConstants.DICT_MAP.get(OperationConstants.LABEL + operationLog.operation()));

        return bmsLogOperation;
    }

    /**
     * 完善操作日志
     *
     * @param bmsLogOperation
     * @param apiResponse
     */
    private void perfectOperationLog(BMSLogOperation bmsLogOperation, ApiResponse apiResponse) {

        try {
            BMSUserContext bmsUserContext = AuthUtils.getCurrentUser();
            bmsLogOperation.setUserId(bmsUserContext.getId());
            bmsLogOperation.setUsername(bmsUserContext.getUsername());
        } catch (Exception e) {
            log.error("用户未登录 -- 在进行操作！！！请注意！！ ");
        }

        try {

            bmsLogOperation.setApiResponse(objectMapper.writeValueAsString(apiResponse));
            Map<String, String> message = new HashMap<>();
            message.put("message", apiResponse.getMessage());
            bmsLogOperation.setMessage(objectMapper.writeValueAsString(message));
            bmsLogOperation.setCode(apiResponse.getCode());
            bmsLogOperation.setStatus("200".equals(apiResponse.getCode()));

        } catch (JsonProcessingException e) {
            log.error("系统转换Json异常：{} --- {}", e.getMessage(), apiResponse);
        }
    }


}
